//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "OLSR_HelloPiggybacking.h"

#define state_      (*state_ptr)

Define_Module(OLSR_HelloPiggybacking);

OLSR_HelloPiggybacking::OLSR_HelloPiggybacking() {

}

OLSR_HelloPiggybacking::~OLSR_HelloPiggybacking() {
    // TODO Auto-generated destructor stub
}

void OLSR_HelloPiggybacking::initialize(int stage) {
    OLSR::initialize(stage);

    if (stage==4)
    {
        // inizialize my info
        piggyback_my_info.address = getAddress();
        piggyback_my_info.c_cell = 0;
        piggyback_my_info.c_wifi = 0;
        piggyback_my_info.distance = 0;
        piggyback_my_info.energy = 0;
        piggyback_my_info.role = STEM_STUB;

        db = check_and_cast<KnowledgeRepository *>(this->getParentModule()->getSubmodule("udpApp", 0)->getSubmodule("db"));
    }
}

bool
OLSR_HelloPiggybacking::process_hello(OLSR_msg& msg, const nsaddr_t &receiver_iface, const nsaddr_t &sender_iface, const int &index)
{
    // first check the piggybacked field
    //EV << "Piggybacked messages: " << msg.hello().countPiggyback << endl;
    for (int i = 0; i < msg.hello().countPiggyback; i++) {
        node_state_info_t *info = (node_state_info_t *)msg.hello().hello_piggyback(i).buffPiggyBack;
        db->updateFromHello(*info);

        //EV << "Piggybacked address: " << info->address << endl;
    }

    return OLSR::process_hello(msg, receiver_iface, sender_iface, index);
}

///
/// \brief Creates a new %OLSR HELLO message which is buffered to be sent later on.
///
void
OLSR_HelloPiggybacking::send_hello()
{
    OLSR_msg msg;
    double now = CURRENT_TIME;
    msg.msg_type() = OLSR_HELLO_MSG;
    msg.vtime() = OLSR::seconds_to_emf(OLSR_NEIGHB_HOLD_TIME);
    msg.orig_addr() = ra_addr();
    msg.ttl() = 1;
    msg.hop_count() = 0;
    msg.msg_seq_num() = msg_seq();

    msg.hello().reserved() = 0;
    msg.hello().htime() = OLSR::seconds_to_emf(SIMTIME_DBL(hello_ival()));
    msg.hello().willingness() = willingness();
    msg.hello().count = 0;

    std::map<uint8_t, int> linkcodes_count;
    for (linkset_t::iterator it = linkset().begin(); it != linkset().end(); it++)
    {
        OLSR_link_tuple* link_tuple = *it;
        if (get_main_addr(link_tuple->local_iface_addr()) == ra_addr() && link_tuple->time() >= now)
        {
            uint8_t link_type, nb_type, link_code;

            // Establishes link type
            if (use_mac() && link_tuple->lost_time() >= now)
                link_type = OLSR_LOST_LINK;
            else if (link_tuple->sym_time() >= now)
                link_type = OLSR_SYM_LINK;
            else if (link_tuple->asym_time() >= now)
                link_type = OLSR_ASYM_LINK;
            else
                link_type = OLSR_LOST_LINK;
            // Establishes neighbor type.
            if (state_.find_mpr_addr(get_main_addr(link_tuple->nb_iface_addr())))
                nb_type = OLSR_MPR_NEIGH;
            else
            {
                bool ok = false;
                for (nbset_t::iterator nb_it = nbset().begin();
                        nb_it != nbset().end();
                        nb_it++)
                {
                    OLSR_nb_tuple* nb_tuple = *nb_it;
                    if (nb_tuple->nb_main_addr() == get_main_addr(link_tuple->nb_iface_addr()))
                    {
                        if (nb_tuple->getStatus() == OLSR_STATUS_SYM)
                            nb_type = OLSR_SYM_NEIGH;
                        else if (nb_tuple->getStatus() == OLSR_STATUS_NOT_SYM)
                            nb_type = OLSR_NOT_NEIGH;
                        else
                        {
                            error ("There is a neighbor tuple with an unknown status!");
                        }
                        ok = true;
                        break;
                    }
                }
                if (!ok)
                {
                    EV << "I don't know the neighbor " << get_main_addr(link_tuple->nb_iface_addr()) << "!!! \n";
                    continue;
                }
            }

            int count = msg.hello().count;
            link_code = (link_type & 0x03) | ((nb_type << 2) & 0x0f);
            std::map<uint8_t, int>::iterator pos = linkcodes_count.find(link_code);
            if (pos == linkcodes_count.end())
            {
                linkcodes_count[link_code] = count;
                assert(count >= 0 && count < OLSR_MAX_HELLOS);
                msg.hello().hello_msg(count).count = 0;
                msg.hello().hello_msg(count).link_code() = link_code;
                msg.hello().hello_msg(count).reserved() = 0;
                msg.hello().count++;
            }
            else
                count = (*pos).second;

            int i = msg.hello().hello_msg(count).count;
            assert(count >= 0 && count < OLSR_MAX_HELLOS);
            assert(i >= 0 && i < OLSR_MAX_ADDRS);

            msg.hello().hello_msg(count).nb_iface_addr(i) =
                link_tuple->nb_iface_addr();
            msg.hello().hello_msg(count).count++;
            msg.hello().hello_msg(count).link_msg_size() =
                msg.hello().hello_msg(count).size();
        }
    }

    // add piggyback informations
    if (MAX_SIZE_PIGGYBACK > sizeof(node_state_info_t)) {
        int j = 0;
        node_state_info_t *n_info;

        memset(msg.hello().hello_piggyback(j).buffPiggyBack, 0, MAX_SIZE_PIGGYBACK);
        msg.hello().hello_piggyback(j).sizebuff = sizeof(node_state_info_t);
        n_info = (node_state_info_t *) (msg.hello().hello_piggyback(j).buffPiggyBack);
        *n_info = piggyback_my_info;

        j++;

        for(std::list<node_state_info_t>::iterator it_pb = piggyback_neighbourhood_info.begin(); it_pb != piggyback_neighbourhood_info.end(); it_pb++ ) {

            memset(msg.hello().hello_piggyback(j).buffPiggyBack, 0, MAX_SIZE_PIGGYBACK);
            msg.hello().hello_piggyback(j).sizebuff = sizeof(node_state_info_t);
            n_info = (node_state_info_t *) (msg.hello().hello_piggyback(j).buffPiggyBack);
            *n_info = *it_pb;

            j++;
        }

        msg.hello().countPiggyback = piggyback_neighbourhood_info.size() + 1;
    }

    msg.msg_size() = msg.size();

    enque_msg(msg, JITTER);
}

void OLSR_HelloPiggybacking::clearPiggyBackInfo(void) {
    piggyback_neighbourhood_info.clear();
}

void OLSR_HelloPiggybacking::setMyPiggyBackInfo(void *info_p) {
    node_state_info_t *info = (node_state_info_t *)info_p;
    piggyback_my_info = *info;
}

bool OLSR_HelloPiggybacking::addNewPiggyBackInfo(void *info_p) {
    node_state_info_t *info = (node_state_info_t *)info_p;

    if(piggyback_neighbourhood_info.size() < (OLSR_MAX_PIGGYBACK - 1)) {
        piggyback_neighbourhood_info.push_back(*info);

        return true;
    }

    return false;
}

